﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using TByte.XnaHelper.Input;
using TByte.XnaHelper.GameObjects;

namespace TByte.XnaHelper
{
        /// <summary>
        /// This class is the in-game console. (toggle with consoleKey)
        /// </summary>
        public static class GameConsole
        {
            #region Fields
            static bool hidden = true;
            static List<ConsoleMessage> consoleLog = new List<ConsoleMessage>();
            static List<String> history = new List<string>();
            static Dictionary<string, Action<string[]>> commands = new Dictionary<string, Action<string[]>>();
            static int scrollOffset = 0;
            static int historyOffset = 0;
            #endregion

            #region Methods
            public static void HandleInput(object sender, CharacterEventArgs e)
            {
                if (!hidden)
                {
                    //Console is open, process typing
                    switch (e.Character)
                    {
                        default:
                            //Unhandled key, so add to string
                            if(Manager.basicFont.Characters.Contains(e.Character))
                                currentString += e.Character;
                            break;
                        case '\r':
                            if (currentString == string.Empty)
                                return;
                            //Found return. Run command
                            GameConsole.RunCommand(currentString);
                            historyOffset = 0; //Reset offset to get back to new line
                            if (history[history.Count() - 1] != String.Empty)
                            {
                                history.Add(String.Empty); //Add new empty string (And save old one)
                            }
                            break;
                        case '\b':
                            if (currentString.Length > 0)
                                currentString = currentString.Substring(0, currentString.Length - 1);
                            break;
                    }
                }
            }
            public static void MouseWheelInput(object sender, MouseEventArgs e)
            {
                if (!hidden)
                {
                    scrollOffset += e.Delta;

                    if (scrollOffset < 0)
                    {
                        scrollOffset = 0;
                    }
                }
            }
            public static void HandleKey(object sender, KeyEventArgs e)
            {
                if((int)e.KeyCode == Manager.SettingInt("keys_toggleConsole"))
                    ToggleHidden();

                if (!hidden)
                {
                    switch (e.KeyCode)
                    {
                        case Keys.Up:
                            historyOffset++;
                            break;
                        case Keys.Down:
                            historyOffset--;
                            break;
                    }
                    if (historyOffset < 0)
                    {
                        historyOffset = 0;
                    }
                    while (history.Count() - 1 - historyOffset <= 0)
                    {
                        if (historyOffset > 0)
                        {
                            historyOffset--;
                        }
                        else
                        {
                            history.Add(String.Empty);
                        }
                    }
                }
            }
            public static void RunCommand(string cmd)
            {
                //split string into params
                string[] parameters = cmd.Split(' ');

                //Check if anything is sent at all
                if (parameters.Count() > 0)
                {
                    switch (parameters[0])
                    {
                        case "commands":
                            //Gather all commands
                            List<string> commandNames = commands.Keys.ToList();
                        
                            //Add hardcoded commands
                            commandNames.AddRange(new List<String> { 
                                "commands", 
                                "gamestate", 
                                "objectlist", 
                                "settings_save", 
                                "settings_load", 
                                "applyChanges" });

                            //Sort commands
                            commandNames.Sort();

                            //Display all commands
                            GameConsole.Notice("Commands:");
                            foreach (string name in commandNames)
                            {
                                GameConsole.Notice(name);
                            }
                            break;
                        case "gamestate":
                            if (parameters.Count() > 1)
                            {
                                if (Manager.GameStateList.ContainsKey(parameters[1]))
                                {
                                    Manager.GameState = Manager.GameStateList[parameters[1]];
                                    GameConsole.Notice("Game state set to '" + parameters[1] + "'.");
                                }
                                else
                                {
                                    GameConsole.ErrorLog("The game state '" + parameters[1] + "' does not exist.");
                                }
                            }
                            else
                            {
                                WriteLine("Please enter a gamestate: gamestate <GameState Name>.");
                            }
                            break;
                        case "objectlist":
                            if (parameters.Count() > 1)
                            {
                                if (Manager.GameStateList.ContainsKey(parameters[1]))
                                {
                                    if (parameters.Count() > 2)
                                    {
                                        try
                                        {
                                            int depth = Convert.ToInt32(parameters[2]);

                                            PrintChildrenNames(Manager.GameStateList[parameters[1]].Root, depth);
                                        }
                                        catch
                                        {
                                            WriteLine("Usage: objectlist <Game State> <Maximum Depth>.");
                                        }
                                    }
                                    else
                                    {
                                        PrintChildrenNames(Manager.GameStateList[parameters[1]].Root);
                                    }
                                }
                                else
                                {
                                    GameConsole.ErrorLog("The game state '" + parameters[1] + "' does not exist.");
                                }
                            }
                            else
                            {
                                PrintChildrenNames(Manager.GameState.Root);
                            }
                            break;
                        case "settings_save":
                            Manager.SaveSettings();
                            GameConsole.Notice("Settings have been saved.");
                            break;
                        case "settings_load":
                            Manager.LoadSettings();
                            GameConsole.Notice("Settings have been loaded.");
                            break;
                        case "applyChanges":
                            Manager.ApplyChanges();
                            break;
                        default: //Command not hardcoded
                            if (commands.ContainsKey(parameters[0]))
                            {
                                //Command found in usercommands
                                commands[parameters[0]].Invoke(parameters);
                            }
                            else if (Manager.SettingsList.ContainsKey(parameters[0]))
                            {
                                //This command is a setting
                                if (parameters.Count() > 1)
                                {
                                    int value;
                                    if (Int32.TryParse(parameters[1], out value))
                                    {
                                        Manager.SettingInt(parameters[0], value, true);
                                        GameConsole.Notice(parameters[0] + " has been set to '" + parameters[1] + "'.");
                                    }
                                    else
                                    {
                                        GameConsole.ErrorLog(parameters[1] + " is not a valid value.");
                                    }
                                }
                                else
                                {
                                    GameConsole.WriteLine("'" + parameters[0] + "'    " + Manager.Setting(parameters[0]).ToString());
                                }
                            }
                            else
                            {
                                //Command does not exist at all
                                GameConsole.ErrorLog("The command '" + parameters[0] + "' does not exist.");
                            }
                            break;
                    }
                }

            }
            static void ToggleHidden()
            {
                if (hidden)
                {
                    hidden = false;
                    historyOffset = 0;
                    if (history.Count() > 0)
                    {
                        if (history[history.Count() - 1] != String.Empty)
                        {
                            history.Add(String.Empty);
                        }
                    }
                    else
                    {
                        history.Add(String.Empty);
                    }
                }
                else
                {
                    hidden = true;
                }
            }
            public static void Draw(SpriteBatch spriteBatch)
            {
                if (!hidden)
                {
                    //Current typing command
                    string msg = ">  " + currentString + "|";
                    Vector2 stringSize = Manager.basicFont.MeasureString(msg);
                    int consoleHeight = (int)Manager.Screen.Y * 3 / 4;
                    int consoleLogCount = (int)((consoleHeight - stringSize.Y) / stringSize.Y);

                    spriteBatch.Begin();
                    //Draw bg
                    spriteBatch.Draw(Manager.dummyTexture, new Rectangle(0, 0, (int)Manager.Screen.X, consoleHeight), Color.Black * 0.8f);

                    //Draw console log
                    for (int i = 1; i <= consoleLog.Count() + scrollOffset && i <= consoleLogCount && consoleLog.Count() - i - scrollOffset >= 0; i++)
                    {
                        spriteBatch.DrawString(Manager.basicFont, consoleLog[consoleLog.Count() - i - scrollOffset].Message, new Vector2(0, consoleHeight - stringSize.Y * (i + 1)), consoleLog[consoleLog.Count() - i - scrollOffset].Color);
                    }

                    //Draw current string
                    spriteBatch.DrawString(Manager.basicFont, msg, new Vector2(0, consoleHeight - stringSize.Y), Color.White);

                    spriteBatch.End();
                }
            }
            public static void WriteLine(string line)
            {
                consoleLog.Add(new ConsoleMessage(line));
            }
            public static void ErrorLog(string line)
            {
                consoleLog.Add(new ConsoleMessage(line, Color.Red));
            }
            public static void Notice(string line)
            {
                consoleLog.Add(new ConsoleMessage(line, Color.LightGreen));
            }
            public static void WriteColored(string line, Color color)
            {
                consoleLog.Add(new ConsoleMessage(line, color));
            }
            public static void AddCommand(string command, Action<string[]> action)
            {
                if (!commands.ContainsKey(command))
                {
                    commands.Add(command, action);
                }
                else
                {
                    GameConsole.WriteLine("The command '" + command + "' cannot be added. It already exists");
                }
            }
            static void PrintChildrenNames(GameObject gameObject, int maxDepth = -1, int currentDepth = 0)
            {
                string msg = String.Empty;
                //Add tabs
                for (int i = currentDepth; i > 0; i--)
                {
                    msg += " ";
                }

                //Print Self
                WriteLine(msg + gameObject.Name);

                if (currentDepth == maxDepth)
                {
                    return;
                }

                //Add space
                currentDepth++;

                //Print all children
                foreach (GameObject child in gameObject.Children)
                {
                    PrintChildrenNames(child, maxDepth, currentDepth);
                }
            }
            #endregion

            #region Properties
            public static Boolean Visible { get { return !hidden; } }
            static String currentString
            {
                get
                {
                    return history[history.Count() - 1 - historyOffset];
                }
                set
                {
                    history[history.Count() - 1 - historyOffset] = value;
                }
            }
            #endregion

            #region Classes
            class ConsoleMessage
            {
                public string Message;
                public Color Color;

                public ConsoleMessage(string message, Color color)
                {
                    this.Color = color;
                    this.Message = message;
                }
                public ConsoleMessage(string message)
                {
                    this.Color = Color.White;
                    this.Message = message;
                }
            }
            #endregion
        }
}
